﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using MavlinkDebugger.PacketViewer;
using SLPropertyGrid;

namespace MavlinkPacketViewer.PacketViewer
{
    /// <summary>
    /// PropertyGrid (read only)
    /// </summary>
    public partial class PropertyGrid : ContentControl
    {
        internal static Color backgroundColor = Color.FromArgb(255, 233, 236, 250);
        internal static Color backgroundColorFocused = Color.FromArgb(255, 94, 170, 255);

        static Type thisType = typeof(PropertyGrid);

        ValueEditorBase selectedEditor;
        ScrollViewer LayoutRoot;
        Grid MainGrid;
        TextBlock DescriptionTextBlock;
        bool _loaded = false;
        bool _resetLoadedObject;


        /// <summary>
        /// Constructor
        /// </summary>
        public PropertyGrid()
        {
            base.DefaultStyleKey = typeof(PropertyGrid);
            this.Loaded += PropertyGrid_Loaded;
        }

        public static readonly DependencyProperty SelectedObjectProperty =
          DependencyProperty.Register("SelectedObject", typeof(object), thisType, new PropertyMetadata(null, OnSelectedObjectChanged));

        public object SelectedObject
        {
            get { return base.GetValue(SelectedObjectProperty); }
            set { base.SetValue(SelectedObjectProperty, value); }
        }

        private static void OnSelectedObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var propertyGrid = d as PropertyGrid;
            if (propertyGrid != null)
            {
                if (!propertyGrid._loaded)
                    propertyGrid._resetLoadedObject = true;
                else if (null != e.NewValue)
                    propertyGrid.ResetObject(e.NewValue);
                else
                    propertyGrid.ResetMainGrid();
            }
        }

        public static readonly DependencyProperty GridBorderBrushProperty =
            DependencyProperty.Register("GridBorderBrush", typeof(Brush), thisType, new PropertyMetadata(new SolidColorBrush(Colors.LightGray), OnGridBorderBrushChanged));

        /// <summary>
        /// Gets or sets the Border Brush of the Property Grid
        /// </summary>
        public Brush GridBorderBrush
        {
            get { return (Brush)base.GetValue(GridBorderBrushProperty); }
            set { base.SetValue(GridBorderBrushProperty, value); }
        }

        private static void OnGridBorderBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            PropertyGrid propertyGrid = d as PropertyGrid;
            if (propertyGrid != null && null != propertyGrid.LayoutRoot && null != e.NewValue)
                propertyGrid.LayoutRoot.BorderBrush = (SolidColorBrush)e.NewValue;
        }


        public static readonly DependencyProperty GridBorderThicknessProperty =
            DependencyProperty.Register("GridBorderThickness", typeof(Thickness), thisType, new PropertyMetadata(new Thickness(1), OnGridBorderThicknessChanged));

        /// <summary>
        /// Gets or sets the Border Thickness of the Property Grid
        /// </summary>
        public Thickness GridBorderThickness
        {
            get { return (Thickness)base.GetValue(GridBorderThicknessProperty); }
            set { base.SetValue(GridBorderThicknessProperty, value); }
        }


        private static void OnGridBorderThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            PropertyGrid propertyGrid = d as PropertyGrid;
            if (propertyGrid != null && null != propertyGrid.LayoutRoot && null != e.NewValue)
                propertyGrid.LayoutRoot.BorderThickness = (Thickness)e.NewValue;
        }
        
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.LayoutRoot = (ScrollViewer)this.GetTemplateChild("LayoutRoot");
            this.MainGrid = (Grid)this.GetTemplateChild("MainGrid");
            this.DescriptionTextBlock = (TextBlock)this.GetTemplateChild("Description");

            _loaded = true;

            if (_resetLoadedObject)
            {
                _resetLoadedObject = false;
                this.ResetObject(this.SelectedObject);
            }
        }

        int SetObject(object obj)
        {
            var props = new List<PropertyItem>();
            int rowCount = -1;

            // Parse the objects properties
            props = ParseObject(obj);

            var categories = (from p in props // todo: ditch category?
                              orderby p.Category
                              select p.Category).Distinct();

            foreach (string category in categories)
            {
                this.AddHeaderRow(category, ref rowCount);

                var items = from p in props
                            where p.Category == category
                            orderby p.Name
                            select p;

                foreach (var item in items)
                    this.AddPropertyRow(item, ref rowCount);

            }

            return rowCount++;
        }

        void ResetObject(object obj)
        {
            this.ResetMainGrid();

            int rowCount = this.SetObject(obj);

            if (rowCount > 0)
                AddGridSplitter(rowCount);
        }

        void ResetMainGrid()
        {
            this.MainGrid.Children.Clear();
            this.MainGrid.RowDefinitions.Clear();
            DescriptionTextBlock.Text = String.Empty;
        }


        void AddHeaderRow(string category, ref int rowIndex)
        {
            rowIndex++;
            MainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(21) });

            #region Column 0 - Margin

            Border brd = GetCategoryMargin(category, GetHideImage(Visibility.Visible), GetShowImage(Visibility.Collapsed));
            MainGrid.Children.Add(brd);
            Grid.SetRow(brd, rowIndex);
            Grid.SetColumn(brd, 0);

            #endregion

            #region Column 1 & 2 - Category Header

            brd = GetCategoryHeader(category);
            MainGrid.Children.Add(brd);
            Grid.SetRow(brd, rowIndex);
            Grid.SetColumn(brd, 1);
            Grid.SetColumnSpan(brd, 2);

            #endregion
        }

        void AddPropertyRow(PropertyItem item, ref int rowIndex)
        {
            #region Create Display Objects
            PropertyGridLabel label = CreateLabel(item.Name, item.DisplayName);
            ValueEditorBase editor = EditorService.GetEditor(item, label);
            if (null == editor)
                return;
            editor.GotFocus += this.Editor_GotFocus;
            #endregion

            rowIndex++;
            MainGrid.RowDefinitions.Add(new RowDefinition());
            string tagValue = item.Category;

            #region Column 0 - Margin
            Border brd = GetItemMargin(tagValue);
            MainGrid.Children.Add(brd);
            Grid.SetRow(brd, rowIndex);
            Grid.SetColumn(brd, 0);
            #endregion

            #region Column 1 - Label
            brd = GetItemLabel(label, tagValue);
            MainGrid.Children.Add(brd);
            Grid.SetRow(brd, rowIndex);
            Grid.SetColumn(brd, 1);
            #endregion

            #region Column 2 - Editor
            brd = GetItemEditor(editor, tagValue);
            MainGrid.Children.Add(brd);
            Grid.SetRow(brd, rowIndex);
            Grid.SetColumn(brd, 2);
            #endregion
        }
        void AddGridSplitter(int rowCount)
        {
            GridSplitter gsp = new GridSplitter()
            {
                IsTabStop = false,
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Stretch,
                Background = new SolidColorBrush(Colors.Transparent),
                ShowsPreview = false,
                Width = 2
            };
            Grid.SetColumn(gsp, 2);
            Grid.SetRowSpan(gsp, rowCount);
            Canvas.SetZIndex(gsp, 1);
            MainGrid.Children.Add(gsp);

        }
        void ToggleCategoryVisible(bool show, string tagValue)
        {
            foreach (FrameworkElement element in this.MainGrid.Children)
            {
                object value = element.Tag;
                if (null != value)
                {
                    string tag = (string)value;
                    if (tagValue == tag)
                        element.Visibility = show ? Visibility.Visible : Visibility.Collapsed;
                }
            }
        }
        void AttachWheelEvents()
        {
            if (HtmlPage.IsEnabled)
            {
                HtmlPage.Window.AttachEvent("DOMMouseScroll", OnMouseWheel);
                HtmlPage.Window.AttachEvent("onmousewheel", OnMouseWheel);
                HtmlPage.Document.AttachEvent("onmousewheel", OnMouseWheel);
            }
        }
        void DetachWheelEvents()
        {
            if (HtmlPage.IsEnabled)
            {
                HtmlPage.Window.DetachEvent("DOMMouseScroll", OnMouseWheel);
                HtmlPage.Window.DetachEvent("onmousewheel", OnMouseWheel);
                HtmlPage.Document.DetachEvent("onmousewheel", OnMouseWheel);
            }
        }
        Image GetHideImage(Visibility visibility)
        {
            Image img = GetImage("/SLPropertyGrid;component/Assets/minus.png");
            img.Visibility = visibility;
            img.MouseLeftButtonUp += new MouseButtonEventHandler(this.CategoryHide_MouseLeftButtonUp);
            return img;
        }
        Image GetShowImage(Visibility visibility)
        {
            var img = GetImage("/SLPropertyGrid;component/Assets/plus.png");
            img.Visibility = visibility;
            img.MouseLeftButtonUp += this.CategoryShow_MouseLeftButtonUp;
            return img;
        }

        static List<PropertyItem> ParseObject(object objItem)
        {
            if (null == objItem)
                return new List<PropertyItem>();

            var pc = new List<PropertyItem>();
            var t = objItem.GetType();

            foreach (var propInfo in t.GetProperties().Where(MemberIsBrowsable))
            {
                object value = propInfo.GetValue(objItem, null);
                var prop = new PropertyInfoItem(value, propInfo, IsMemberReadOnly(propInfo));
                pc.Add(prop);
            }

            foreach (var fieldInfo in t.GetFields().Where(MemberIsBrowsable))
            {
                object value = fieldInfo.GetValue(objItem);
                var fieldItem = new FieldItem(value, fieldInfo, MemberIsBrowsable(fieldInfo));
                pc.Add(fieldItem);
            }

            return pc;
        }

        private static bool IsMemberReadOnly(PropertyInfo propertyInfo)
        {
            bool readOnly = false;
            var attr = propertyInfo.GetAttribute<ReadOnlyAttribute>();
            if (attr != null)
                readOnly = attr.IsReadOnly;
            return readOnly;
        }


        private static bool MemberIsBrowsable(MemberInfo memberInfo)
        {
            bool isBrowsable = true;
            var b = memberInfo.GetAttribute<BrowsableAttribute>();
            if (null != b)
                isBrowsable = b.Browsable;

            if (isBrowsable)
            {
                var eb = memberInfo.GetAttribute<EditorBrowsableAttribute>();
                if (null != eb && eb.State == EditorBrowsableState.Never)
                    isBrowsable = false;
            }

            return isBrowsable;
        }


        private static PropertyGridLabel CreateLabel(string name, string displayName)
        {
            var txt = new TextBlock()
            {
                Text = displayName,
                Margin = new Thickness(0),
                Foreground = new SolidColorBrush(Colors.Black)
            };
            return new PropertyGridLabel()
            {
                Name = Guid.NewGuid().ToString("N"),
                Content = txt
            };
        }


        private static Border GetCategoryMargin(string tagValue, Image hide, Image show)
        {
            var stp = new StackPanel()
            {
                Name = Guid.NewGuid().ToString("N"),
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };
            stp.Tag = tagValue;
            stp.Children.Add(hide);
            stp.Children.Add(show);

            var brd = new Border() { Background = new SolidColorBrush(backgroundColor) };
            brd.Child = stp;

            return brd;
        }
    
        private static Border GetCategoryHeader(string category)
        {
            var txt = new TextBlock
            {
                Name = Guid.NewGuid().ToString("N"),
                Text = category,
                VerticalAlignment = VerticalAlignment.Center,
                HorizontalAlignment = HorizontalAlignment.Left,
                Foreground = new SolidColorBrush(Colors.Gray),
                Margin = new Thickness(3, 0, 0, 0),
                FontWeight = FontWeights.Bold,
            };

            var brd = new Border
                          {
                              Background = new SolidColorBrush(backgroundColor),
                              Child = txt
                          };
            Canvas.SetZIndex(brd, 1);
            return brd;
        }


        private static Border GetItemMargin(string tagValue)
        {
            return new Border()
            {
                Name = Guid.NewGuid().ToString("N"),
                Margin = new Thickness(0),
                BorderThickness = new Thickness(0),
                Background = new SolidColorBrush(backgroundColor),
                Tag = tagValue
            };
        }
        
        private static Border GetItemLabel(PropertyGridLabel label, string tagValue)
        {
            return new Border()
            {
                Name = Guid.NewGuid().ToString("N"),
                Margin = new Thickness(0),
                BorderBrush = new SolidColorBrush(backgroundColor),
                BorderThickness = new Thickness(0, 0, 1, 1),
                Child = label,
                Tag = tagValue
            };
        }
      
        private static Border GetItemEditor(ValueEditorBase editor, string tagValue)
        {
            var brd = new Border
            {
                Name = Guid.NewGuid().ToString("N"),
                Margin = new Thickness(1, 0, 0, 0),
                BorderThickness = new Thickness(0, 0, 0, 1),
                BorderBrush = new SolidColorBrush(backgroundColor)
            };
            brd.Child = editor;
            brd.Tag = tagValue;
            return brd;
        }

        private static Image GetImage(string imageUri)
        {
            var img = new Image()
            {
                Name = Guid.NewGuid().ToString("N"),
                Source = new BitmapImage(new Uri(imageUri, UriKind.Relative)),
                Height = 9,
                Width = 9,
                VerticalAlignment = VerticalAlignment.Center,
                HorizontalAlignment = HorizontalAlignment.Center
            };
            return img;
        }


        #region Event Handlers
       
        private void PropertyGrid_Loaded(object sender, RoutedEventArgs e)
        {
            this.MouseEnter += PropertyGrid_MouseEnter;
            this.MouseLeave += PropertyGrid_MouseLeave;
        }

        private void Editor_GotFocus(object sender, RoutedEventArgs e)
        {
            if (null != selectedEditor)
                selectedEditor.IsSelected = false;

            selectedEditor = sender as ValueEditorBase;

            if (null != selectedEditor)
            {
                selectedEditor.IsSelected = true;
                DescriptionTextBlock.Text = selectedEditor.Property.Description;

                //double editorX = ((UIElement)selectedEditor.Parent).RenderTransformOrigin.X;
                //Debug.WriteLine("editorX: " + editorX.ToString());
                //double editorY = ((UIElement)selectedEditor.Parent).RenderTransformOrigin.Y;
                //Debug.WriteLine("editorY: " + editorY.ToString());

                //double thisWidth = this.RenderSize.Width;
                //Debug.WriteLine("thisWidth: " + thisWidth.ToString());
                //double thisHeight = this.RenderSize.Height;
                //Debug.WriteLine("thisHeight: " + thisHeight.ToString());
            }
        }

        private void PropertyGrid_MouseEnter(object sender, MouseEventArgs e)
        {
            this.AttachWheelEvents();
        }
        private void PropertyGrid_MouseLeave(object sender, MouseEventArgs e)
        {
            this.DetachWheelEvents();
        }
        private void OnMouseWheel(object sender, HtmlEventArgs args)
        {
            double mouseDelta = 0;
            ScriptObject e = args.EventObject;

            // Mozilla and Safari    
            if (e.GetProperty("detail") != null)
            {
                mouseDelta = ((double)e.GetProperty("detail"));
            }

                // IE and Opera    
            else if (e.GetProperty("wheelDelta") != null)
                mouseDelta = ((double)e.GetProperty("wheelDelta"));

            mouseDelta = Math.Sign(mouseDelta);
            mouseDelta = mouseDelta * -1;
            mouseDelta = mouseDelta * 40; // Just a guess at an acceleration
            mouseDelta = this.LayoutRoot.VerticalOffset + mouseDelta;
            this.LayoutRoot.ScrollToVerticalOffset(mouseDelta);
        }

        private void CategoryHide_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            FrameworkElement ctl = sender as FrameworkElement;
            Panel stp = ctl.Parent as Panel;
            string tagValue = (string)stp.Tag;
            stp.Children[0].Visibility = Visibility.Collapsed;
            stp.Children[1].Visibility = Visibility.Visible;
            this.Dispatcher.BeginInvoke(delegate()
            {
                ToggleCategoryVisible(false, tagValue);
            });
        }

        private void CategoryShow_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            FrameworkElement ctl = sender as FrameworkElement;
            Panel stp = ctl.Parent as Panel;
            string tagValue = (string)stp.Tag;
            stp.Children[0].Visibility = Visibility.Visible;
            stp.Children[1].Visibility = Visibility.Collapsed;
            this.Dispatcher.BeginInvoke(delegate()
            {
                ToggleCategoryVisible(true, tagValue);
            });
        }
        #endregion
    }
}
